package utils

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
	uuid "github.com/nu7hatch/gouuid"
)

const (
	DefaultExpireTime    = 20 * time.Second
	WatchDogWorkStepTime = 10 * time.Second

	//lua.go
	LuaCheckAndDelete = `
		if(redis.call('get',KEYS[1])==ARGV[1]) then
			return redis.call('del',KEYS[1])
		else
			return 0
		end
	`
	LuaCheckAndRenewal = `
		if(redis.call('get',KEYS[1])==ARGV[1]) then
			return redis.call('expire', KEYS[1] , ARGV[2])
		else
			return 0
		end
	
	`
)

type RedisLock struct {
	expireTime       time.Duration
	watchDogWorkTime time.Duration
	key              string
	redisCli         *redis.Client
	redisBack        context.Context
	id               string
	signal           chan int
}

//优点：
//简单易用： 代码逻辑相对简单，易于理解和使用。
//基于 Redis： 使用 Redis 作为分布式锁的存储介质，具有高性能和可靠性。
//支持过期时间： 锁具有过期时间，避免了死锁的情况。
//支持续期机制： 实现了续期机制，保证了锁的持续有效。
//缺点：
// 只试用于特定场合
//不支持阻塞等待： 代码中的 Lock 函数是非阻塞的，如果锁已被其他客户端持有，则会直接返回获取锁失败，不支持阻塞等待获取锁。
//仅适用于单实例 Redis： 该实现仅适用于单实例 Redis，不支持 Redis 集群模式。
//性能考虑： 代码中的看门狗机制会定期续期锁的过期时间，这可能会对性能产生一定影响，特别是在锁被频繁获取和释放的情况下。

func NewRedisLock(cli *redis.Client, context context.Context, key string, exp time.Duration) *RedisLock {
	u, err := uuid.NewV4()
	if err != nil {
		return nil
	}
	id := strings.Join(strings.Split(u.String(), "-"), "")
	lock := RedisLock{
		key:              key,
		redisCli:         cli,
		expireTime:       exp,
		redisBack:        context,
		id:               id,
		signal:           make(chan int, 1),
		watchDogWorkTime: exp / 2,
	}
	return &lock
}

func (lock *RedisLock) tryLock() (bool, error) {
	return lock.redisCli.SetNX(lock.redisBack, lock.key, lock.id, lock.expireTime).Result()
}

func (lock *RedisLock) Lock() (bool, error) {
	success, err := lock.tryLock()
	if success && err == nil {
		// 开启看们狗
		go lock.watchDog()
		return success, err
	}
	//非阻塞加锁失败的话，直接返回错误
	return false, err
}

// lua 脚本保证解锁原子
func (lock *RedisLock) UnLock() {
	script := redis.NewScript(LuaCheckAndDelete)
	res, err := script.Run(lock.redisBack, lock.redisCli, []string{lock.key}, lock.id).Int64()
	if err != nil || res != 1 {
		return
	}
	lock.unwatchDog()
}
func (lock *RedisLock) watchDog() {
	//开启一个定时器
	ticker := time.NewTicker(lock.watchDogWorkTime)
	defer ticker.Stop()

	for {
		select {
		case <-lock.signal:
			return
		case <-ticker.C:
			fmt.Println("原子续期")
			script := redis.NewScript(LuaCheckAndRenewal)
			// 采用lua脚本 过期时间是秒为基本单位
			res, err := script.Run(lock.redisBack, lock.redisCli, []string{lock.key}, lock.id, lock.expireTime / time.Second).Int64()
			if err != nil || res != 1 {
				return
			}
			//lock.redisCli.Set(lock.redisBack, lock.key, lock.id, lock.expireTime)
		}
	}
}

func (lock *RedisLock) unwatchDog() {
	lock.signal <- 1
}
